Ktor Module 可以用來組織程式碼,本身僅是一個 Application 類別的 extension function,讓 server 啟動時執行而已。Ktor 並沒有規定 Module 內部應該如何實作,也沒有限制 Module 的顆粒度要多大,官方文件只提到 module 可以是某些 plugin 及 routes 的集合。例如我可以實作一個 Shutdown module,其 extension function 的內部實作是安裝 Ktor ShutdownUrl plugin,藉此增加1個 GET route,讓外界呼叫此 GET API 後能停止 server。
實作上,我把每個子專案當作是一個 Ktor module,而且都依賴於 infrastrcture module。infrastrcture module 負責安裝 Ktor Plugin,提供底層的函式庫及功能。我建立了2個子專案 ops 及 club,透過設定 ktor.application.modules 屬性值,Ktor 會在啟動時依序執行這3個 module 的 extension fuction。設定時要注意 infra module 必須要放在第一個位置。
application {
modules = [
fanpoll.infra.ApplicationKt.main,
fanpoll.ops.OpsProjectKt.opsMain,
fanpoll.club.ClubProjectKt.clubMain
]
}
其中 infra module 的 extension function 內部實作是負責初始化 Ktor plugin 及 ProjectManager
fun Application.main() {
install(LoggingFeature)
install(DatabaseFeature)
install(RedisFeature)
install(Authentication)
// ...以下省略
koin {
modules(
module(createdAtStart = true) {
single { ProjectManager(get()) }
}
)
}
// ...以下省略
}
club module 的 extension function 內部實作是負責初始化 club 專案。第一步是先透過 Koin DI 取得 ProjectManager 物件,然後載入 club 專案設定檔,最後再建立 club Project 物件並註冊至 ProjectManager
每個子專案可各自定義以下項目,infra module 再根據 project 物件執行相對應的功能
fun Application.clubMain() {
val projectManager = get<ProjectManager>()
val projectConfig = ProjectManager.loadConfig<ClubConfig>(ClubConst.projectId)
projectManager.register(
Project(
ClubConst.projectId,
projectConfig.auth.principalSourceAuthConfigs,
ClubUserType.values().map { it.value },
ClubOpenApi.Instance,
ClubNotification.AllTypes
)
)
// ...以下省略
}
class Project(
override val id: String,
val principalSourceAuthConfigs: List<PrincipalSourceAuthConfig>,
val userTypes: List<UserType>,
val projectOpenApi: ProjectOpenApi,
val notificationTypes: List<NotificationType>? = null
) : IdentifiableObject<String>()
今天從程式開發層面說明如何使用 Ktor Module 進行模組化開發,明天將會從建置部署層面切入,說明如何使用 Gradle Multi-Project Builds 建置專案、還有使用 Gradle Shadow Plugin 及 Docker Compose 打包部署。